package com.fasterxml.jackson.databind.util;

import java.io.*;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.TreeMap;

import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.core.base.ParserMinimalBase;
import com.fasterxml.jackson.core.io.JsonEOFException;
import com.fasterxml.jackson.core.io.NumberInput;
import com.fasterxml.jackson.core.json.JsonWriteContext;
import com.fasterxml.jackson.core.util.ByteArrayBuilder;
import com.fasterxml.jackson.core.util.JacksonFeatureSet;
import com.fasterxml.jackson.databind.*;

/**
 * 添加<code>public void writeEnum(Enum<?> anEnum) throws IOException</code>和<code>public Object getObject() throws IOException</code>方法
 */
public class TokenBuffer extends JsonGenerator {
    protected final static int DEFAULT_GENERATOR_FEATURES = JsonGenerator.Feature.collectDefaults();
    protected ObjectCodec _objectCodec;
    protected JsonStreamContext _parentContext;
    protected int _generatorFeatures;
    protected StreamReadConstraints _streamReadConstraints = StreamReadConstraints.defaults();
    protected boolean _closed;
    protected boolean _hasNativeTypeIds;
    protected boolean _hasNativeObjectIds;
    protected boolean _mayHaveNativeIds;
    protected boolean _forceBigDecimal;
    protected Segment _first;
    protected Segment _last;
    protected int _appendAt;
    protected Object _typeId;
    protected Object _objectId;
    protected boolean _hasNativeId = false;
    protected JsonWriteContext _writeContext;

    public TokenBuffer(ObjectCodec codec, boolean hasNativeIds) {
        _objectCodec = codec;
        _generatorFeatures = DEFAULT_GENERATOR_FEATURES;
        _writeContext = JsonWriteContext.createRootContext(null);
        _first = _last = new Segment();
        _appendAt = 0;
        _hasNativeTypeIds = hasNativeIds;
        _hasNativeObjectIds = hasNativeIds;
        _mayHaveNativeIds = _hasNativeTypeIds || _hasNativeObjectIds;
    }

    public TokenBuffer(JsonParser p) {
        this(p, null);
    }

    public TokenBuffer(JsonParser p, DeserializationContext ctxt) {
        _objectCodec = p.getCodec();
        _streamReadConstraints = p.streamReadConstraints();
        _parentContext = p.getParsingContext();
        _generatorFeatures = DEFAULT_GENERATOR_FEATURES;
        _writeContext = JsonWriteContext.createRootContext(null);
        _first = _last = new Segment();
        _appendAt = 0;
        _hasNativeTypeIds = p.canReadTypeId();
        _hasNativeObjectIds = p.canReadObjectId();
        _mayHaveNativeIds = _hasNativeTypeIds || _hasNativeObjectIds;
        _forceBigDecimal = (ctxt == null) ? false : ctxt.isEnabled(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS);
    }

    @Deprecated
    public static TokenBuffer asCopyOfValue(JsonParser p) throws IOException {
        TokenBuffer b = new TokenBuffer(p);
        b.copyCurrentStructure(p);
        return b;
    }

    public TokenBuffer overrideParentContext(JsonStreamContext ctxt) {
        _parentContext = ctxt;
        return this;
    }

    public TokenBuffer forceUseOfBigDecimal(boolean b) {
        _forceBigDecimal = b;
        return this;
    }

    @Override
    public Version version() {
        return com.fasterxml.jackson.databind.cfg.PackageVersion.VERSION;
    }

    public JsonParser asParser() {
        return asParser(_objectCodec);
    }

    public JsonParser asParserOnFirstToken() throws IOException {
        JsonParser p = asParser(_objectCodec);
        p.nextToken();
        return p;
    }

    public JsonParser asParser(ObjectCodec codec) {
        return new Parser(_first, codec, _hasNativeTypeIds, _hasNativeObjectIds, _parentContext, _streamReadConstraints);
    }

    public JsonParser asParser(StreamReadConstraints streamReadConstraints) {
        return new Parser(_first, _objectCodec, _hasNativeTypeIds, _hasNativeObjectIds, _parentContext, streamReadConstraints);
    }

    public JsonParser asParser(JsonParser src) {
        Parser p = new Parser(_first, src.getCodec(), _hasNativeTypeIds, _hasNativeObjectIds, _parentContext, src.streamReadConstraints());
        p.setLocation(src.currentTokenLocation());
        return p;
    }

    public JsonToken firstToken() {
        return _first.type(0);
    }

    public boolean isEmpty() {
        return (_appendAt == 0) && (_first == _last);
    }

    @SuppressWarnings("resource")
    public TokenBuffer append(TokenBuffer other) throws IOException {
        if (!_hasNativeTypeIds) {
            _hasNativeTypeIds = other.canWriteTypeId();
        }
        if (!_hasNativeObjectIds) {
            _hasNativeObjectIds = other.canWriteObjectId();
        }
        _mayHaveNativeIds = _hasNativeTypeIds || _hasNativeObjectIds;

        JsonParser p = other.asParser();
        while (p.nextToken() != null) {
            copyCurrentStructure(p);
        }
        return this;
    }

    public void serialize(JsonGenerator gen) throws IOException {
        Segment segment = _first;
        int ptr = -1;

        final boolean checkIds = _mayHaveNativeIds;
        boolean hasIds = checkIds && (segment.hasIds());

        while (true) {
            if (++ptr >= Segment.TOKENS_PER_SEGMENT) {
                ptr = 0;
                segment = segment.next();
                if (segment == null) break;
                hasIds = checkIds && (segment.hasIds());
            }
            JsonToken t = segment.type(ptr);
            if (t == null) break;

            if (hasIds) {
                Object id = segment.findObjectId(ptr);
                if (id != null) {
                    gen.writeObjectId(id);
                }
                id = segment.findTypeId(ptr);
                if (id != null) {
                    gen.writeTypeId(id);
                }
            }

            switch (t) {
                case START_OBJECT:
                    gen.writeStartObject();
                    break;
                case END_OBJECT:
                    gen.writeEndObject();
                    break;
                case START_ARRAY:
                    gen.writeStartArray();
                    break;
                case END_ARRAY:
                    gen.writeEndArray();
                    break;
                case FIELD_NAME: {
                    Object ob = segment.get(ptr);
                    if (ob instanceof SerializableString) {
                        gen.writeFieldName((SerializableString) ob);
                    } else {
                        gen.writeFieldName((String) ob);
                    }
                }
                break;
                case VALUE_STRING: {
                    Object ob = segment.get(ptr);
                    if (ob instanceof SerializableString) {
                        gen.writeString((SerializableString) ob);
                    } else {
                        gen.writeString((String) ob);
                    }
                }
                break;
                case VALUE_NUMBER_INT: {
                    Object n = segment.get(ptr);
                    if (n instanceof Integer) {
                        gen.writeNumber((Integer) n);
                    } else if (n instanceof BigInteger) {
                        gen.writeNumber((BigInteger) n);
                    } else if (n instanceof Long) {
                        gen.writeNumber((Long) n);
                    } else if (n instanceof Short) {
                        gen.writeNumber((Short) n);
                    } else {
                        gen.writeNumber(((Number) n).intValue());
                    }
                }
                break;
                case VALUE_NUMBER_FLOAT: {
                    Object n = segment.get(ptr);
                    if (n instanceof Double) {
                        gen.writeNumber((Double) n);
                    } else if (n instanceof BigDecimal) {
                        gen.writeNumber((BigDecimal) n);
                    } else if (n instanceof Float) {
                        gen.writeNumber((Float) n);
                    } else if (n == null) {
                        gen.writeNull();
                    } else if (n instanceof String) {
                        gen.writeNumber((String) n);
                    } else {
                        _reportError(String.format("Unrecognized value type for VALUE_NUMBER_FLOAT: %s, cannot serialize", n.getClass().getName()));
                    }
                }
                break;
                case VALUE_TRUE:
                    gen.writeBoolean(true);
                    break;
                case VALUE_FALSE:
                    gen.writeBoolean(false);
                    break;
                case VALUE_NULL:
                    gen.writeNull();
                    break;
                case VALUE_EMBEDDED_OBJECT: {
                    Object value = segment.get(ptr);
                    if (value instanceof RawValue) {
                        ((RawValue) value).serialize(gen);
                    } else if (value instanceof JsonSerializable) {
                        gen.writeObject(value);
                    } else {
                        gen.writeEmbeddedObject(value);
                    }
                }
                break;
                default:
                    throw new RuntimeException("Internal error: should never end up through this code path");
            }
        }
    }

    public TokenBuffer deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        if (!p.hasToken(JsonToken.FIELD_NAME)) {
            copyCurrentStructure(p);
            return this;
        }

        JsonToken t;
        writeStartObject();
        do {
            copyCurrentStructure(p);
        } while ((t = p.nextToken()) == JsonToken.FIELD_NAME);
        if (t != JsonToken.END_OBJECT) {
            ctxt.reportWrongTokenException(TokenBuffer.class, JsonToken.END_OBJECT, "Expected END_OBJECT after copying contents of a JsonParser into TokenBuffer, got " + t);
        }
        writeEndObject();
        return this;
    }

    @Override
    @SuppressWarnings("resource")
    public String toString() {

        final int MAX_COUNT = 100;
        StringBuilder sb = new StringBuilder();
        sb.append("[TokenBuffer: ");
        JsonParser jp = asParser();
        int count = 0;
        final boolean hasNativeIds = _hasNativeTypeIds || _hasNativeObjectIds;

        while (true) {
            JsonToken t;
            try {
                t = jp.nextToken();
                if (t == null) break;

                if (hasNativeIds) {
                    _appendNativeIds(sb);
                }

                if (count < MAX_COUNT) {
                    if (count > 0) {
                        sb.append(", ");
                    }
                    sb.append(t);
                    if (t == JsonToken.FIELD_NAME) {
                        sb.append('(');
                        sb.append(jp.currentName());
                        sb.append(')');
                    }
                }
            } catch (IOException ioe) {
                throw new IllegalStateException(ioe);
            }
            ++count;
        }

        if (count >= MAX_COUNT) {
            sb.append(" ... (truncated ").append(count - MAX_COUNT).append(" entries)");
        }
        sb.append(']');
        return sb.toString();
    }

    private void _appendNativeIds(StringBuilder sb) {
        Object objectId = _last.findObjectId(_appendAt - 1);
        if (objectId != null) {
            sb.append("[objectId=").append(objectId).append(']');
        }
        Object typeId = _last.findTypeId(_appendAt - 1);
        if (typeId != null) {
            sb.append("[typeId=").append(typeId).append(']');
        }
    }

    @Override
    public JsonGenerator enable(Feature f) {
        _generatorFeatures |= f.getMask();
        return this;
    }

    @Override
    public JsonGenerator disable(Feature f) {
        _generatorFeatures &= ~f.getMask();
        return this;
    }

    @Override
    public boolean isEnabled(Feature f) {
        return (_generatorFeatures & f.getMask()) != 0;
    }

    @Override
    public int getFeatureMask() {
        return _generatorFeatures;
    }

    @Override
    @Deprecated
    public JsonGenerator setFeatureMask(int mask) {
        _generatorFeatures = mask;
        return this;
    }

    @Override
    public JsonGenerator overrideStdFeatures(int values, int mask) {
        int oldState = getFeatureMask();
        _generatorFeatures = (oldState & ~mask) | (values & mask);
        return this;
    }

    @Override
    public JsonGenerator useDefaultPrettyPrinter() {
        return this;
    }

    @Override
    public JsonGenerator setCodec(ObjectCodec oc) {
        _objectCodec = oc;
        return this;
    }

    @Override
    public ObjectCodec getCodec() {
        return _objectCodec;
    }

    @Override
    public final JsonWriteContext getOutputContext() {
        return _writeContext;
    }

    @Override
    public boolean canWriteBinaryNatively() {
        return true;
    }

    @Override
    public JacksonFeatureSet<StreamWriteCapability> getWriteCapabilities() {
        return DEFAULT_WRITE_CAPABILITIES;
    }

    @Override
    public void flush() {
    }

    @Override
    public void close() throws IOException {
        _closed = true;
    }

    @Override
    public boolean isClosed() {
        return _closed;
    }

    @Override
    public final void writeStartArray() {
        _writeContext.writeValue();
        _appendStartMarker(JsonToken.START_ARRAY);
        _writeContext = _writeContext.createChildArrayContext();
    }

    @Override
    public void writeStartArray(Object forValue) {
        _writeContext.writeValue();
        _appendStartMarker(JsonToken.START_ARRAY);
        _writeContext = _writeContext.createChildArrayContext(forValue);
    }

    @Override
    public void writeStartArray(Object forValue, int size) {
        _writeContext.writeValue();
        _appendStartMarker(JsonToken.START_ARRAY);
        _writeContext = _writeContext.createChildArrayContext(forValue);
    }

    @Override
    public final void writeEndArray() {
        _appendEndMarker(JsonToken.END_ARRAY);
        JsonWriteContext c = _writeContext.getParent();
        if (c != null) {
            _writeContext = c;
        }
    }

    @Override
    public final void writeStartObject() {
        _writeContext.writeValue();
        _appendStartMarker(JsonToken.START_OBJECT);
        _writeContext = _writeContext.createChildObjectContext();
    }

    @Override
    public void writeStartObject(Object forValue) {
        _writeContext.writeValue();
        _appendStartMarker(JsonToken.START_OBJECT);
        _writeContext = _writeContext.createChildObjectContext(forValue);
    }

    @Override
    public void writeStartObject(Object forValue, int size) {
        _writeContext.writeValue();
        _appendStartMarker(JsonToken.START_OBJECT);
        _writeContext = _writeContext.createChildObjectContext(forValue);
    }

    @Override
    public final void writeEndObject() {
        _appendEndMarker(JsonToken.END_OBJECT);
        JsonWriteContext c = _writeContext.getParent();
        if (c != null) {
            _writeContext = c;
        }
    }

    @Override
    public final void writeFieldName(String name) throws IOException {
        _writeContext.writeFieldName(name);
        _appendFieldName(name);
    }

    @Override
    public void writeFieldName(SerializableString name) throws IOException {
        _writeContext.writeFieldName(name.getValue());
        _appendFieldName(name);
    }

    @Override
    public void writeString(String text) throws IOException {
        if (text == null) {
            writeNull();
        } else {
            _appendValue(JsonToken.VALUE_STRING, text);
        }
    }

    @Override
    public void writeString(char[] text, int offset, int len) throws IOException {
        writeString(new String(text, offset, len));
    }

    @Override
    public void writeString(SerializableString text) throws IOException {
        if (text == null) {
            writeNull();
        } else {
            _appendValue(JsonToken.VALUE_STRING, text);
        }
    }

    @Override
    public void writeString(Reader reader, final int len) throws IOException {
        if (reader == null) {
            _reportError("null reader");
        }
        int toRead = (len >= 0) ? len : Integer.MAX_VALUE;

        final char[] buf = new char[1000];
        StringBuilder sb = new StringBuilder(1000);
        while (toRead > 0) {
            int toReadNow = Math.min(toRead, buf.length);
            int numRead = reader.read(buf, 0, toReadNow);
            if (numRead <= 0) {
                break;
            }
            sb.append(buf, 0, numRead);
            toRead -= numRead;
        }
        if (toRead > 0 && len >= 0) {
            _reportError("Was not able to read enough from reader");
        }
        _appendValue(JsonToken.VALUE_STRING, sb.toString());
    }

    @Override
    public void writeRawUTF8String(byte[] text, int offset, int length) {
        _reportUnsupportedOperation();
    }

    @Override
    public void writeUTF8String(byte[] text, int offset, int length) {
        _reportUnsupportedOperation();
    }

    @Override
    public void writeRaw(String text) throws IOException {
        _reportUnsupportedOperation();
    }

    @Override
    public void writeRaw(String text, int offset, int len) throws IOException {
        _reportUnsupportedOperation();
    }

    @Override
    public void writeRaw(SerializableString text) throws IOException {
        _reportUnsupportedOperation();
    }

    @Override
    public void writeRaw(char[] text, int offset, int len) throws IOException {
        _reportUnsupportedOperation();
    }

    @Override
    public void writeRaw(char c) throws IOException {
        _reportUnsupportedOperation();
    }

    @Override
    public void writeRawValue(String text) {
        _appendValue(JsonToken.VALUE_EMBEDDED_OBJECT, new RawValue(text));
    }

    @Override
    public void writeRawValue(String text, int offset, int len) {
        if (offset > 0 || len != text.length()) {
            text = text.substring(offset, offset + len);
        }
        _appendValue(JsonToken.VALUE_EMBEDDED_OBJECT, new RawValue(text));
    }

    @Override
    public void writeRawValue(char[] text, int offset, int len) {
        _appendValue(JsonToken.VALUE_EMBEDDED_OBJECT, new String(text, offset, len));
    }

    @Override
    public void writeNumber(short i) throws IOException {
        _appendValue(JsonToken.VALUE_NUMBER_INT, i);
    }

    @Override
    public void writeNumber(int i) throws IOException {
        _appendValue(JsonToken.VALUE_NUMBER_INT, i);
    }

    @Override
    public void writeNumber(long l) throws IOException {
        _appendValue(JsonToken.VALUE_NUMBER_INT, l);
    }

    @Override
    public void writeNumber(double d) throws IOException {
        _appendValue(JsonToken.VALUE_NUMBER_FLOAT, d);
    }

    @Override
    public void writeNumber(float f) throws IOException {
        _appendValue(JsonToken.VALUE_NUMBER_FLOAT, f);
    }

    @Override
    public void writeNumber(BigDecimal dec) throws IOException {
        if (dec == null) {
            writeNull();
        } else {
            _appendValue(JsonToken.VALUE_NUMBER_FLOAT, dec);
        }
    }

    @Override
    public void writeNumber(BigInteger v) throws IOException {
        if (v == null) {
            writeNull();
        } else {
            _appendValue(JsonToken.VALUE_NUMBER_INT, v);
        }
    }

    @Override
    public void writeNumber(String encodedValue) throws IOException {
        _appendValue(JsonToken.VALUE_NUMBER_FLOAT, encodedValue);
    }

    private void writeLazyInteger(Object encodedValue) {
        _appendValue(JsonToken.VALUE_NUMBER_INT, encodedValue);
    }

    private void writeLazyDecimal(Object encodedValue) {
        _appendValue(JsonToken.VALUE_NUMBER_FLOAT, encodedValue);
    }

    @Override
    public void writeBoolean(boolean state) {
        _appendValue(state ? JsonToken.VALUE_TRUE : JsonToken.VALUE_FALSE);
    }

    @Override
    public void writeNull() {
        _appendValue(JsonToken.VALUE_NULL);
    }

    @Override
    public void writeObject(Object value) throws IOException {
        if (value == null) {
            writeNull();
            return;
        }
        Class<?> raw = value.getClass();
        if (raw == byte[].class || (value instanceof RawValue)) {
            _appendValue(JsonToken.VALUE_EMBEDDED_OBJECT, value);
            return;
        }
        if (_objectCodec == null) {
            _appendValue(JsonToken.VALUE_EMBEDDED_OBJECT, value);
        } else {
            _objectCodec.writeValue(this, value);
        }
    }

    @Override
    public void writeTree(TreeNode node) throws IOException {
        if (node == null) {
            writeNull();
            return;
        }

        if (_objectCodec == null) {
            _appendValue(JsonToken.VALUE_EMBEDDED_OBJECT, node);
        } else {
            _objectCodec.writeTree(this, node);
        }
    }

    @Override
    public void writeEnum(Enum<?> anEnum) {
        if (anEnum == null) {
            writeNull();
            return;
        }
        _appendValue(JsonToken.VALUE_ENUM, anEnum);
    }

    @Override
    public void writeBinary(Base64Variant b64variant, byte[] data, int offset, int len) throws IOException {
        byte[] copy = new byte[len];
        System.arraycopy(data, offset, copy, 0, len);
        writeObject(copy);
    }

    @Override
    public int writeBinary(Base64Variant b64variant, InputStream data, int dataLength) {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean canWriteTypeId() {
        return _hasNativeTypeIds;
    }

    @Override
    public boolean canWriteObjectId() {
        return _hasNativeObjectIds;
    }

    @Override
    public void writeTypeId(Object id) {
        _typeId = id;
        _hasNativeId = true;
    }

    @Override
    public void writeObjectId(Object id) {
        _objectId = id;
        _hasNativeId = true;
    }

    @Override
    public void writeEmbeddedObject(Object object) {
        _appendValue(JsonToken.VALUE_EMBEDDED_OBJECT, object);
    }

    @Override
    public void copyCurrentEvent(JsonParser p) throws IOException {
        if (_mayHaveNativeIds) {
            _checkNativeIds(p);
        }
        switch (p.currentToken()) {
            case START_OBJECT:
                writeStartObject();
                break;
            case END_OBJECT:
                writeEndObject();
                break;
            case START_ARRAY:
                writeStartArray();
                break;
            case END_ARRAY:
                writeEndArray();
                break;
            case FIELD_NAME:
                writeFieldName(p.currentName());
                break;
            case VALUE_STRING:
                if (p.hasTextCharacters()) {
                    writeString(p.getTextCharacters(), p.getTextOffset(), p.getTextLength());
                } else {
                    writeString(p.getText());
                }
                break;
            case VALUE_NUMBER_INT:
                switch (p.getNumberType()) {
                    case INT:
                        writeNumber(p.getIntValue());
                        break;
                    case BIG_INTEGER:
                        writeLazyInteger(p.getNumberValueDeferred());
                        break;
                    default:
                        writeNumber(p.getLongValue());
                }
                break;
            case VALUE_NUMBER_FLOAT:
                writeLazyDecimal(p.getNumberValueDeferred());
                break;
            case VALUE_TRUE:
                writeBoolean(true);
                break;
            case VALUE_FALSE:
                writeBoolean(false);
                break;
            case VALUE_NULL:
                writeNull();
                break;
            case VALUE_EMBEDDED_OBJECT:
                writeObject(p.getEmbeddedObject());
                break;
            default:
                throw new RuntimeException("Internal error: unexpected token: " + p.currentToken());
        }
    }

    @Override
    public void copyCurrentStructure(JsonParser p) throws IOException {
        JsonToken t = p.currentToken();

        if (t == JsonToken.FIELD_NAME) {
            if (_mayHaveNativeIds) {
                _checkNativeIds(p);
            }
            writeFieldName(p.currentName());
            t = p.nextToken();
        } else if (t == null) {
            throw new JsonEOFException(p, null, "Unexpected end-of-input");
        }

        switch (t) {
            case START_ARRAY:
                if (_mayHaveNativeIds) {
                    _checkNativeIds(p);
                }
                writeStartArray();
                _copyBufferContents(p);
                break;
            case START_OBJECT:
                if (_mayHaveNativeIds) {
                    _checkNativeIds(p);
                }
                writeStartObject();
                _copyBufferContents(p);
                break;
            case END_ARRAY:
                writeEndArray();
                break;
            case END_OBJECT:
                writeEndObject();
                break;
            default:
                _copyBufferValue(p, t);
        }
    }

    protected void _copyBufferContents(JsonParser p) throws IOException {
        int depth = 1;
        JsonToken t;

        while ((t = p.nextToken()) != null) {
            switch (t) {
                case FIELD_NAME:
                    if (_mayHaveNativeIds) {
                        _checkNativeIds(p);
                    }
                    writeFieldName(p.currentName());
                    break;
                case START_ARRAY:
                    if (_mayHaveNativeIds) {
                        _checkNativeIds(p);
                    }
                    writeStartArray();
                    ++depth;
                    break;
                case START_OBJECT:
                    if (_mayHaveNativeIds) {
                        _checkNativeIds(p);
                    }
                    writeStartObject();
                    ++depth;
                    break;
                case END_ARRAY:
                    writeEndArray();
                    if (--depth == 0) {
                        return;
                    }
                    break;
                case END_OBJECT:
                    writeEndObject();
                    if (--depth == 0) {
                        return;
                    }
                    break;
                default:
                    _copyBufferValue(p, t);
            }
        }
    }

    private void _copyBufferValue(JsonParser p, JsonToken t) throws IOException {
        if (_mayHaveNativeIds) {
            _checkNativeIds(p);
        }
        switch (t) {
            case VALUE_STRING:
                if (p.hasTextCharacters()) {
                    writeString(p.getTextCharacters(), p.getTextOffset(), p.getTextLength());
                } else {
                    writeString(p.getText());
                }
                break;
            case VALUE_NUMBER_INT:
                switch (p.getNumberType()) {
                    case INT:
                        writeNumber(p.getIntValue());
                        break;
                    case BIG_INTEGER:
                        writeLazyInteger(p.getNumberValueDeferred());
                        break;
                    default:
                        writeNumber(p.getLongValue());
                }
                break;
            case VALUE_NUMBER_FLOAT:
                writeLazyDecimal(p.getNumberValueDeferred());
                break;
            case VALUE_TRUE:
                writeBoolean(true);
                break;
            case VALUE_FALSE:
                writeBoolean(false);
                break;
            case VALUE_NULL:
                writeNull();
                break;
            case VALUE_EMBEDDED_OBJECT:
                writeObject(p.getEmbeddedObject());
                break;
            default:
                throw new RuntimeException("Internal error: unexpected token: " + t);
        }
    }

    private void _checkNativeIds(JsonParser p) throws IOException {
        if ((_typeId = p.getTypeId()) != null) {
            _hasNativeId = true;
        }
        if ((_objectId = p.getObjectId()) != null) {
            _hasNativeId = true;
        }
    }

    protected final void _appendValue(JsonToken type) {
        _writeContext.writeValue();
        Segment next;
        if (_hasNativeId) {
            next = _last.append(_appendAt, type, _objectId, _typeId);
        } else {
            next = _last.append(_appendAt, type);
        }
        if (next == null) {
            ++_appendAt;
        } else {
            _last = next;
            _appendAt = 1;
        }
    }

    protected final void _appendValue(JsonToken type, Object value) {
        _writeContext.writeValue();
        Segment next;
        if (_hasNativeId) {
            next = _last.append(_appendAt, type, value, _objectId, _typeId);
        } else {
            next = _last.append(_appendAt, type, value);
        }
        if (next == null) {
            ++_appendAt;
        } else {
            _last = next;
            _appendAt = 1;
        }
    }

    protected final void _appendFieldName(Object value) {
        Segment next;
        if (_hasNativeId) {
            next = _last.append(_appendAt, JsonToken.FIELD_NAME, value, _objectId, _typeId);
        } else {
            next = _last.append(_appendAt, JsonToken.FIELD_NAME, value);
        }
        if (next == null) {
            ++_appendAt;
        } else {
            _last = next;
            _appendAt = 1;
        }
    }

    protected final void _appendStartMarker(JsonToken type) {
        Segment next;
        if (_hasNativeId) {
            next = _last.append(_appendAt, type, _objectId, _typeId);
        } else {
            next = _last.append(_appendAt, type);
        }
        if (next == null) {
            ++_appendAt;
        } else {
            _last = next;
            _appendAt = 1;
        }
    }

    protected final void _appendEndMarker(JsonToken type) {
        Segment next = _last.append(_appendAt, type);
        if (next == null) {
            ++_appendAt;
        } else {
            _last = next;
            _appendAt = 1;
        }
    }

    @Override
    protected void _reportUnsupportedOperation() {
        throw new UnsupportedOperationException("Called operation not supported for TokenBuffer");
    }

    protected final static class Parser extends ParserMinimalBase {
        protected ObjectCodec _codec;
        protected StreamReadConstraints _streamReadConstraints;
        protected final boolean _hasNativeTypeIds;
        protected final boolean _hasNativeObjectIds;
        protected final boolean _hasNativeIds;
        protected Segment _segment;
        protected int _segmentPtr;
        protected TokenBufferReadContext _parsingContext;
        protected boolean _closed;
        protected transient ByteArrayBuilder _byteBuilder;
        protected JsonLocation _location = null;

        @Deprecated
        public Parser(Segment firstSeg, ObjectCodec codec, boolean hasNativeTypeIds, boolean hasNativeObjectIds) {
            this(firstSeg, codec, hasNativeTypeIds, hasNativeObjectIds, null);
        }

        @Deprecated
        public Parser(Segment firstSeg, ObjectCodec codec, boolean hasNativeTypeIds, boolean hasNativeObjectIds, JsonStreamContext parentContext) {
            this(firstSeg, codec, hasNativeTypeIds, hasNativeObjectIds, parentContext, StreamReadConstraints.defaults());
        }

        public Parser(Segment firstSeg, ObjectCodec codec, boolean hasNativeTypeIds, boolean hasNativeObjectIds, JsonStreamContext parentContext, StreamReadConstraints streamReadConstraints) {
            super();
            _segment = firstSeg;
            _segmentPtr = -1;
            _codec = codec;
            _streamReadConstraints = streamReadConstraints;
            _parsingContext = TokenBufferReadContext.createRootContext(parentContext);
            _hasNativeTypeIds = hasNativeTypeIds;
            _hasNativeObjectIds = hasNativeObjectIds;
            _hasNativeIds = (hasNativeTypeIds || hasNativeObjectIds);
        }

        public void setLocation(JsonLocation l) {
            _location = l;
        }

        @Override
        public ObjectCodec getCodec() {
            return _codec;
        }

        @Override
        public void setCodec(ObjectCodec c) {
            _codec = c;
        }

        @Override
        public Version version() {
            return com.fasterxml.jackson.databind.cfg.PackageVersion.VERSION;
        }

        @Override
        public JacksonFeatureSet<StreamReadCapability> getReadCapabilities() {
            return DEFAULT_READ_CAPABILITIES;
        }

        @Override
        public StreamReadConstraints streamReadConstraints() {
            return _streamReadConstraints;
        }

        public JsonToken peekNextToken() {
            if (_closed) return null;
            Segment seg = _segment;
            int ptr = _segmentPtr + 1;
            if (ptr >= Segment.TOKENS_PER_SEGMENT) {
                ptr = 0;
                seg = (seg == null) ? null : seg.next();
            }
            return (seg == null) ? null : seg.type(ptr);
        }

        @Override
        public void close() throws IOException {
            if (!_closed) {
                _closed = true;
            }
        }

        @Override
        public JsonToken nextToken() throws IOException {
            if (_closed || (_segment == null)) return null;
            if (++_segmentPtr >= Segment.TOKENS_PER_SEGMENT) {
                _segmentPtr = 0;
                _segment = _segment.next();
                if (_segment == null) {
                    return null;
                }
            }
            _currToken = _segment.type(_segmentPtr);

            if (_currToken == JsonToken.FIELD_NAME) {
                Object ob = _currentObject();
                String name = (ob instanceof String) ? ((String) ob) : ob.toString();
                _parsingContext.setCurrentName(name);
            } else if (_currToken == JsonToken.START_OBJECT) {
                _parsingContext = _parsingContext.createChildObjectContext();
            } else if (_currToken == JsonToken.START_ARRAY) {
                _parsingContext = _parsingContext.createChildArrayContext();
            } else if (_currToken == JsonToken.END_OBJECT || _currToken == JsonToken.END_ARRAY) {
                _parsingContext = _parsingContext.parentOrCopy();
            } else {
                _parsingContext.updateForValue();
            }
            return _currToken;
        }

        @Override
        public String nextFieldName() throws IOException {
            if (_closed || (_segment == null)) {
                return null;
            }

            int ptr = _segmentPtr + 1;
            if ((ptr < Segment.TOKENS_PER_SEGMENT) && (_segment.type(ptr) == JsonToken.FIELD_NAME)) {
                _segmentPtr = ptr;
                _currToken = JsonToken.FIELD_NAME;
                Object ob = _segment.get(ptr);
                String name = (ob instanceof String) ? ((String) ob) : ob.toString();
                _parsingContext.setCurrentName(name);
                return name;
            }
            return (nextToken() == JsonToken.FIELD_NAME) ? currentName() : null;
        }

        @Override
        public boolean isClosed() {
            return _closed;
        }

        @Override
        public JsonStreamContext getParsingContext() {
            return _parsingContext;
        }

        @Override
        public JsonLocation currentLocation() {
            return (_location == null) ? JsonLocation.NA : _location;
        }

        @Override
        public JsonLocation currentTokenLocation() {
            return currentLocation();
        }

        @Deprecated
        @Override
        public JsonLocation getTokenLocation() {
            return currentTokenLocation();
        }

        @Deprecated
        @Override
        public JsonLocation getCurrentLocation() {
            return currentLocation();
        }

        @Override
        public String currentName() {
            if (_currToken == JsonToken.START_OBJECT || _currToken == JsonToken.START_ARRAY) {
                JsonStreamContext parent = _parsingContext.getParent();
                return parent.getCurrentName();
            }
            return _parsingContext.getCurrentName();
        }

        @Override
        public void overrideCurrentName(String name) {
            JsonStreamContext ctxt = _parsingContext;
            if (_currToken == JsonToken.START_OBJECT || _currToken == JsonToken.START_ARRAY) {
                ctxt = ctxt.getParent();
            }
            if (ctxt instanceof TokenBufferReadContext) {
                try {
                    ((TokenBufferReadContext) ctxt).setCurrentName(name);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }

        @Deprecated
        @Override
        public String getCurrentName() {
            return currentName();
        }

        @Override
        public String getText() {
            if (_currToken == JsonToken.VALUE_STRING || _currToken == JsonToken.FIELD_NAME) {
                Object ob = _currentObject();
                if (ob instanceof String) {
                    return (String) ob;
                }
                return ClassUtil.nullOrToString(ob);
            }
            if (_currToken == null) {
                return null;
            }
            switch (_currToken) {
                case VALUE_NUMBER_INT:
                case VALUE_NUMBER_FLOAT:
                    return ClassUtil.nullOrToString(_currentObject());
                default:
                    return _currToken.asString();
            }
        }

        @Override
        public Object getObject() {
            if (_currToken == JsonToken.VALUE_ENUM) {
                return _currentObject();
            }
            return null;
        }

        @Override
        public char[] getTextCharacters() {
            String str = getText();
            return (str == null) ? null : str.toCharArray();
        }

        @Override
        public int getTextLength() {
            String str = getText();
            return (str == null) ? 0 : str.length();
        }

        @Override
        public int getTextOffset() {
            return 0;
        }

        @Override
        public boolean hasTextCharacters() {
            return false;
        }

        @Override
        public boolean isNaN() {
            if (_currToken == JsonToken.VALUE_NUMBER_FLOAT) {
                Object value = _currentObject();
                if (value instanceof Double) {
                    double v = (Double) value;
                    return !Double.isFinite(v);
                }
                if (value instanceof Float) {
                    float v = (Float) value;
                    return !Double.isFinite(v);
                }
            }
            return false;
        }

        @Override
        public BigInteger getBigIntegerValue() throws IOException {
            Number n = getNumberValue(true);
            if (n instanceof BigInteger) {
                return (BigInteger) n;
            } else if (n instanceof BigDecimal) {
                final BigDecimal bd = (BigDecimal) n;
                streamReadConstraints().validateBigIntegerScale(bd.scale());
                return bd.toBigInteger();
            }
            return BigInteger.valueOf(n.longValue());
        }

        @Override
        public BigDecimal getDecimalValue() throws IOException {
            Number n = getNumberValue(true);
            if (n instanceof BigDecimal) {
                return (BigDecimal) n;
            } else if (n instanceof Integer) {
                return BigDecimal.valueOf(n.intValue());
            } else if (n instanceof Long) {
                return BigDecimal.valueOf(n.longValue());
            } else if (n instanceof BigInteger) {
                return new BigDecimal((BigInteger) n);
            }
            return BigDecimal.valueOf(n.doubleValue());
        }

        @Override
        public double getDoubleValue() throws IOException {
            return getNumberValue().doubleValue();
        }

        @Override
        public float getFloatValue() throws IOException {
            return getNumberValue().floatValue();
        }

        @Override
        public int getIntValue() throws IOException {
            Number n = (_currToken == JsonToken.VALUE_NUMBER_INT) ? ((Number) _currentObject()) : getNumberValue();
            if ((n instanceof Integer) || _smallerThanInt(n)) {
                return n.intValue();
            }
            return _convertNumberToInt(n);
        }

        @Override
        public long getLongValue() throws IOException {
            Number n = (_currToken == JsonToken.VALUE_NUMBER_INT) ? ((Number) _currentObject()) : getNumberValue();
            if ((n instanceof Long) || _smallerThanLong(n)) {
                return n.longValue();
            }
            return _convertNumberToLong(n);
        }

        @Override
        public NumberType getNumberType() throws IOException {
            Object n = getNumberValueDeferred();
            if (n instanceof Integer) return NumberType.INT;
            if (n instanceof Long) return NumberType.LONG;
            if (n instanceof Double) return NumberType.DOUBLE;
            if (n instanceof BigDecimal) return NumberType.BIG_DECIMAL;
            if (n instanceof BigInteger) return NumberType.BIG_INTEGER;
            if (n instanceof Float) return NumberType.FLOAT;
            if (n instanceof Short) return NumberType.INT;
            if (n instanceof String) {
                return (_currToken == JsonToken.VALUE_NUMBER_FLOAT) ? NumberType.BIG_DECIMAL : NumberType.BIG_INTEGER;
            }
            return null;
        }

        @Override
        public NumberTypeFP getNumberTypeFP() {
            if (_currToken == JsonToken.VALUE_NUMBER_FLOAT) {
                Object n = _currentObject();
                if (n instanceof Double) return NumberTypeFP.DOUBLE64;
                if (n instanceof BigDecimal) return NumberTypeFP.BIG_DECIMAL;
                if (n instanceof Float) return NumberTypeFP.FLOAT32;
            }
            return NumberTypeFP.UNKNOWN;
        }

        @Override
        public Number getNumberValue() throws IOException {
            return getNumberValue(false);
        }

        @Override
        public Object getNumberValueDeferred() throws IOException {
            _checkIsNumber();
            return _currentObject();
        }

        private Number getNumberValue(final boolean preferBigNumbers) throws IOException {
            _checkIsNumber();
            Object value = _currentObject();
            if (value instanceof Number) {
                return (Number) value;
            }

            if (value instanceof String) {
                String str = (String) value;
                final int len = str.length();
                if (_currToken == JsonToken.VALUE_NUMBER_INT) {
                    if (preferBigNumbers || (len >= 19)) {
                        return NumberInput.parseBigInteger(str, isEnabled(StreamReadFeature.USE_FAST_BIG_NUMBER_PARSER));
                    }
                    if (len >= 10) {
                        return NumberInput.parseLong(str);
                    }
                    return NumberInput.parseInt(str);
                }
                if (preferBigNumbers) {
                    BigDecimal dec = NumberInput.parseBigDecimal(str, isEnabled(StreamReadFeature.USE_FAST_BIG_NUMBER_PARSER));
                    if (dec == null) {
                        throw new IllegalStateException("Internal error: failed to parse number '" + str + "'");
                    }
                    return dec;
                }
                return NumberInput.parseDouble(str, isEnabled(StreamReadFeature.USE_FAST_DOUBLE_PARSER));
            }
            throw new IllegalStateException("Internal error: entry should be a Number, but is of type " + ClassUtil.classNameOf(value));
        }

        private boolean _smallerThanInt(Number n) {
            return (n instanceof Short) || (n instanceof Byte);
        }

        private boolean _smallerThanLong(Number n) {
            return (n instanceof Integer) || (n instanceof Short) || (n instanceof Byte);
        }

        protected int _convertNumberToInt(Number n) throws IOException {
            if (n instanceof Long) {
                long l = n.longValue();
                int result = (int) l;
                if (((long) result) != l) {
                    reportOverflowInt();
                }
                return result;
            }
            if (n instanceof BigInteger) {
                BigInteger big = (BigInteger) n;
                if (BI_MIN_INT.compareTo(big) > 0 || BI_MAX_INT.compareTo(big) < 0) {
                    reportOverflowInt();
                }
            } else if ((n instanceof Double) || (n instanceof Float)) {
                double d = n.doubleValue();
                if (d < MIN_INT_D || d > MAX_INT_D) {
                    reportOverflowInt();
                }
                return (int) d;
            } else if (n instanceof BigDecimal) {
                BigDecimal big = (BigDecimal) n;
                if (BD_MIN_INT.compareTo(big) > 0 || BD_MAX_INT.compareTo(big) < 0) {
                    reportOverflowInt();
                }
            } else {
                _throwInternal();
            }
            return n.intValue();
        }

        protected long _convertNumberToLong(Number n) throws IOException {
            if (n instanceof BigInteger) {
                BigInteger big = (BigInteger) n;
                if (BI_MIN_LONG.compareTo(big) > 0 || BI_MAX_LONG.compareTo(big) < 0) {
                    reportOverflowLong();
                }
            } else if ((n instanceof Double) || (n instanceof Float)) {
                double d = n.doubleValue();
                if (d < MIN_LONG_D || d > MAX_LONG_D) {
                    reportOverflowLong();
                }
                return (long) d;
            } else if (n instanceof BigDecimal) {
                BigDecimal big = (BigDecimal) n;
                if (BD_MIN_LONG.compareTo(big) > 0 || BD_MAX_LONG.compareTo(big) < 0) {
                    reportOverflowLong();
                }
            } else {
                _throwInternal();
            }
            return n.longValue();
        }

        @Override
        public Object getEmbeddedObject() {
            if (_currToken == JsonToken.VALUE_EMBEDDED_OBJECT) {
                return _currentObject();
            }
            return null;
        }

        @Override
        @SuppressWarnings("resource")
        public byte[] getBinaryValue(Base64Variant b64variant) throws IOException {
            if (_currToken == JsonToken.VALUE_EMBEDDED_OBJECT) {
                Object ob = _currentObject();
                if (ob instanceof byte[]) {
                    return (byte[]) ob;
                }
            }
            if (_currToken != JsonToken.VALUE_STRING) {
                throw _constructError("Current token (" + _currToken + ") not VALUE_STRING (or VALUE_EMBEDDED_OBJECT with byte[]), cannot access as binary");
            }
            final String str = getText();
            if (str == null) {
                return null;
            }
            ByteArrayBuilder builder = _byteBuilder;
            if (builder == null) {
                _byteBuilder = builder = new ByteArrayBuilder(100);
            } else {
                _byteBuilder.reset();
            }
            _decodeBase64(str, builder, b64variant);
            return builder.toByteArray();
        }

        @Override
        public int readBinaryValue(Base64Variant b64variant, OutputStream out) throws IOException {
            byte[] data = getBinaryValue(b64variant);
            if (data != null) {
                out.write(data, 0, data.length);
                return data.length;
            }
            return 0;
        }

        @Override
        public boolean canReadObjectId() {
            return _hasNativeObjectIds;
        }

        @Override
        public boolean canReadTypeId() {
            return _hasNativeTypeIds;
        }

        @Override
        public Object getTypeId() {
            return _segment.findTypeId(_segmentPtr);
        }

        @Override
        public Object getObjectId() {
            return _segment.findObjectId(_segmentPtr);
        }

        protected Object _currentObject() {
            return _segment.get(_segmentPtr);
        }

        protected void _checkIsNumber() throws JacksonException {
            if (_currToken == null || !_currToken.isNumeric()) {
                throw _constructError("Current token (" + _currToken + ") not numeric, cannot use numeric value accessors");
            }
        }

        @Override
        protected void _handleEOF() {
            _throwInternal();
        }
    }

    protected final static class Segment {
        public final static int TOKENS_PER_SEGMENT = 16;
        private final static JsonToken[] TOKEN_TYPES_BY_INDEX;

        static {
            TOKEN_TYPES_BY_INDEX = new JsonToken[16];
            JsonToken[] t = JsonToken.values();
            System.arraycopy(t, 1, TOKEN_TYPES_BY_INDEX, 1, Math.min(15, t.length - 1));
        }

        protected Segment _next;
        protected long _tokenTypes;
        protected final Object[] _tokens = new Object[TOKENS_PER_SEGMENT];
        protected TreeMap<Integer, Object> _nativeIds;

        public Segment() {
        }

        public JsonToken type(int index) {
            long l = _tokenTypes;
            if (index > 0) {
                l >>= (index << 2);
            }
            int ix = ((int) l) & 0xF;
            return TOKEN_TYPES_BY_INDEX[ix];
        }

        public int rawType(int index) {
            long l = _tokenTypes;
            if (index > 0) {
                l >>= (index << 2);
            }
            return ((int) l) & 0xF;
        }

        public Object get(int index) {
            return _tokens[index];
        }

        public Segment next() {
            return _next;
        }

        public boolean hasIds() {
            return _nativeIds != null;
        }

        public Segment append(int index, JsonToken tokenType) {
            if (index < TOKENS_PER_SEGMENT) {
                set(index, tokenType);
                return null;
            }
            _next = new Segment();
            _next.set(0, tokenType);
            return _next;
        }

        public Segment append(int index, JsonToken tokenType, Object objectId, Object typeId) {
            if (index < TOKENS_PER_SEGMENT) {
                set(index, tokenType, objectId, typeId);
                return null;
            }
            _next = new Segment();
            _next.set(0, tokenType, objectId, typeId);
            return _next;
        }

        public Segment append(int index, JsonToken tokenType, Object value) {
            if (index < TOKENS_PER_SEGMENT) {
                set(index, tokenType, value);
                return null;
            }
            _next = new Segment();
            _next.set(0, tokenType, value);
            return _next;
        }

        public Segment append(int index, JsonToken tokenType, Object value, Object objectId, Object typeId) {
            if (index < TOKENS_PER_SEGMENT) {
                set(index, tokenType, value, objectId, typeId);
                return null;
            }
            _next = new Segment();
            _next.set(0, tokenType, value, objectId, typeId);
            return _next;
        }

        private void set(int index, JsonToken tokenType) {
            long typeCode = tokenType.ordinal();
            if (index > 0) {
                typeCode <<= (index << 2);
            }
            _tokenTypes |= typeCode;
        }

        private void set(int index, JsonToken tokenType, Object objectId, Object typeId) {
            long typeCode = tokenType.ordinal();
            if (index > 0) {
                typeCode <<= (index << 2);
            }
            _tokenTypes |= typeCode;
            assignNativeIds(index, objectId, typeId);
        }

        private void set(int index, JsonToken tokenType, Object value) {
            _tokens[index] = value;
            long typeCode = tokenType.ordinal();
            if (index > 0) {
                typeCode <<= (index << 2);
            }
            _tokenTypes |= typeCode;
        }

        private void set(int index, JsonToken tokenType, Object value, Object objectId, Object typeId) {
            _tokens[index] = value;
            long typeCode = tokenType.ordinal();
            if (index > 0) {
                typeCode <<= (index << 2);
            }
            _tokenTypes |= typeCode;
            assignNativeIds(index, objectId, typeId);
        }

        private void assignNativeIds(int index, Object objectId, Object typeId) {
            if (_nativeIds == null) {
                _nativeIds = new TreeMap<Integer, Object>();
            }
            if (objectId != null) {
                _nativeIds.put(_objectIdIndex(index), objectId);
            }
            if (typeId != null) {
                _nativeIds.put(_typeIdIndex(index), typeId);
            }
        }

        Object findObjectId(int index) {
            return (_nativeIds == null) ? null : _nativeIds.get(_objectIdIndex(index));
        }

        Object findTypeId(int index) {
            return (_nativeIds == null) ? null : _nativeIds.get(_typeIdIndex(index));
        }

        private int _typeIdIndex(int i) {
            return i + i;
        }

        private int _objectIdIndex(int i) {
            return i + i + 1;
        }
    }
}
